<?php

declare(strict_types=1);

use mini\{DB, Session, Templates, Url};

/**
 * 获取一个数据表操作对象
 *
 * @param  string $table  数据表名称
 * @param  string $item   默认 db , 对应的数据库一级2配置名称
 * @return object         数据库操作对象
 */
function db($table, $item = 'db'): object
{
    $conf = config($item);
    return DB::getInstance($conf, $table, $item);
}


/**
 * 功能 : 获取一个模型
 * 
 * @param  string $modelName  模型名称
 * @return object             模型对象
 * @throws Exception         如果模型不存在或文件名不合法  
 */
function model(string $name): object
{
    $modelFile = APP_PATH. $name . '/models/' . ucfirst($name).'.php';

    // 确保模型文件存在  
    if (!file_exists($modelFile)) {  
        throw new Exception("模型文件 {$modelFile} 不存在");
    }

    require_once $modelFile;

    $modelName = '\\app\\' . $name . '\\models\\' . ucfirst($name);

    if (class_exists($modelName)) {
        return new $modelName();
    } else {
        throw new Exception("模型 {$modelName} 不存在");
    }
}


/**
 * 读取配置
 */
function config($key = null)
{
    static $conf = null;

    if ($conf === null) {
        $conf = include ROOT_PATH . 'config/app.php';
        if (!is_array($conf)) {
            throw new Exception('未能加载配置文件 config/app.php');
        }
    }

    if ($key === null) {
        return $conf;
    }

    // 处理点分割的键
    if (strpos($key, '.') !== false) {
        $keys = explode('.', $key, 2); // 限制分割成两部分
        if (isset($conf[$keys[0]][$keys[1]])) {
            return $conf[$keys[0]][$keys[1]];
        }
        return null;
    }

    // 处理单个键
    return isset($conf[$key]) ? $conf[$key] : null;
}

/**
 * 加载视图
 *
 * @param  string $path  视图文件名
 * @param  array  $data  分配的变量
 * @return string
 */
function view(string $path, array $data = []): string
{
    $path = str_replace('.', '/', $path);
    $path = explode('/', $path);

    $file = realpath(APP_PATH . $path[0] . '/views/' . $path[1] . '.php');

    if (!$file || !file_exists($file) || is_dir($file)) {
        throw new Exception("模板文件 {$path[0]}/{$path[1]}.php 不存在");
    }

    ob_start();
    extract($data, EXTR_REFS);
    include $file;
    $contents = ob_get_contents() ?: '';
    ob_end_flush();

    // ob_start();  
    // include $file;  
    // $contents = ob_get_clean();

    return $contents;
}

/**
     * 加载模板控制器文件，实例化相应的对象，并调用具有给定数据的指定模板方法。
     *
     * @param string $template_name 要调用的模板方法的名称。
     * @param array $data           要传递给模板方法的数据。
     *
     * @return void
     *
     * @throws Exception 如果找不到模板控制器文件或模板方法不存在。
     */
function template(string $template_name, array $data = []): void
{
    $templates = new Templates();
    $template_controller_path = '';

    if (method_exists($templates, $template_name)) {

        if (!isset($data['view_file'])) {
            $data['view_file'] = METHOD_NAME;
        }

        $templates->$template_name($data);
    } else {
        $template_controller_path = str_replace('../', ROOT_PATH, $template_controller_path);
        die('ERROR: Unable to find ' . $template_name . ' method in ' . $template_controller_path . '.');
    }
}

/**
 * 获取特定的 url 段
 *
 * @param int $num 要检索的段编号。
 * @param string|null $var_type （可选）设置段值的数据类型。默认值为null。
 * @return mixed 指定 url 段的值
 */
function segment(int $num, $var_type = null)
{
    $segments = SEGMENTS;

    array_shift($segments);

    if (isset($segments[$num])) {
        $value = $segments[$num];
    } else {
        $value = '';
    }

    if ($var_type !== null) {
        settype($value, $var_type);
    }

    return $value;
}

/**
 * 获取请求数据，默认已过滤 html 标签等。
 */
function post($field_name, $clean_up = true)
{
    if (!isset($_POST[$field_name])) {
        $value = '';
    } else {
        $value = $_POST[$field_name];

        if ($clean_up) {
            $value = filter_string($value);

            if (is_numeric($value)) {
                $var_type = (strpos($value, '.') !== false) ? 'double' : 'int';
                settype($value, $var_type);
            }
        }
    }

    return $value;
}

/**
 * 获取上一页的 URL（如果可用）。
 *
 * @return string 上一页 URL，如果不存在则返回空字符串
 */
function previous_url() {
    if (isset($_SERVER['HTTP_REFERER'])) {
        $url = $_SERVER['HTTP_REFERER'];
    } else {
        $url = '';
    }
    return $url;
}

/**
 * 获取当前页 URL
 *
 * @return string 当前页 URL
 */
function current_url() {
    $current_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] .  $_SERVER['REQUEST_URI'];
    return $current_url;
}

function asset(string $path = ''): string
{
    return url() . filter_var($path, FILTER_SANITIZE_URL);
}

/**
 * 获取上一页
 */
function referer()
{
    return isset($_SERVER['HTTP_REFERER']) ? htmlspecialchars($_SERVER["HTTP_REFERER"]) : '';
}


/**
 * 消毒数据
 *
 * @param  string $data
 * @return string
 */
function sanitize($data)
{
    return trim(escape($data));
}


/**
 * 获取请求IP
 *
 * @return string 客户端的IP地址
 */
function get_ip() {
    if (isset($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] !== 'unknown') {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    } elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] !== 'unknown') {
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    } elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] !== 'unknown') {
        $ip = $_SERVER['REMOTE_ADDR'];
    } else {
        $ip = '127.0.0.1';
    }

    // 使用正则表达式过滤IP地址，如果匹配成功则返回匹配到的第一个IP，否则返回127.0.0.1
    return preg_match('/[\d\.]{7,15}/', $ip, $matches) ? $matches[0] : '127.0.0.1';
}

/**
 * 闪存消息
 *
 * @param string $type (danger, warning, info, success)
 */
function flash(string $type = '', string $message = '')
{
    // 都不为空则添加消息
    if ($type !== '' && $message !== '') {
        if (isset($_SESSION['flash_messages'][$type])) {
            unset($_SESSION['flash_messages'][$type]);
        }
        // add the message to the session
        $_SESSION['flash_messages'][$type] = [
            'type'    => $type,
            'message' => $message,
        ];
    // 内容为空则显示相应 type 的消息
    } elseif ($type !== '' && $message === '') {
        if (!isset($_SESSION['flash_messages'][$type])) {
            return;
        }
        // get message from the session
        $flash_message = $_SESSION['flash_messages'][$type];
        // delete the flash message
        unset($_SESSION['flash_messages'][$type]);
        // display the flash message
        $flash_message = sprintf(
            '<div class="d-flex justify-content-center align-items-center w-100"><div class="alert alert-%s shadow-sm"><span class="alert-text">%s</span></div></div>',
            $flash_message['name'],
            $flash_message['message']
        );
        echo $flash_message;
    // 类型和内容都为空则显示所有消息
    } elseif ($type === '' && $message === '') {
        if (!isset($_SESSION['flash_messages'])) {
            return;
        }
        // get flash messages
        $flash_messages = $_SESSION['flash_messages'];
        // remove all the flash messages
        unset($_SESSION['flash_messages']);
        // show all flash messages
        foreach ($flash_messages as $flash_message) {
            $flash_message = sprintf(
                '<div class="d-flex justify-content-center align-items-center w-100"><div class="alert alert-%s shadow-sm"><span class="alert-text">%s</span></div></div>',
                $flash_message['type'],
                $flash_message['message']
            );
            echo $flash_message;
        }
    }
}

/**
 * 对指定的 URL 执行 HTTP 重定向。
 *
 * @param  string $target_url 重定向应指向的URL
 * @param  int  $status
 */
function redirect(string $target_url, int $status = 302)
{
    $url = filter_var($target_url, FILTER_SANITIZE_URL);
    header('Location: ' . $url, true, $status);
    die();
}

/**
 * 生成HTML锚（链接）元素。
 *
 * @param string $target_url 要链接到的URL。
 * @param mixed $text 指示没有链接的链接文本或布尔值。
 * @param array|null $attributes （可选）锚点元素的HTML属性的关联数组。
 * @param string|null $additional_code （可选）附加到锚点元素的附加HTML代码。
 * @return string HTML锚元素作为字符串。
 */
function anchor(string $target_url, $text, ?array $attributes = null, ?string $additional_code = null): string
{
    $str = substr($target_url, 0, 4);
    if ($str != 'http') {
        $target_url = BASE_URL . $target_url;
    }

    $target_url = attempt_return_nice_url($target_url);

    $text_type = gettype($text);

    if ($text_type === 'boolean') {
        return $target_url;
    }

    $extra = '';
    if (isset($attributes)) {
        foreach ($attributes as $key => $value) {
            $extra .= ' ' . $key . '="' . $value . '"';
        }
    }

    if (isset($additional_code)) {
        $extra .= ' ' . $additional_code;
    }

    $link = '<a href="' . $target_url . '"' . $extra . '>' . $text . '</a>';
    return $link;
}

/**
 * 为各种输出上下文安全地转义和格式化字符串。
 *
 * @param string $input 要转义的字符串。
 * @param string $encoding （可选）用于转义的字符编码。默认为“UTF-8”。
 * @param string $output_format （可选）所需的输出格式：“html”（默认）、“xml”、“json”、“javascript”或“attribute”。
 *
 * @return string 已准备好安全包含在指定上下文中的转义和格式化字符串。
 * @throws Exception 如果提供了不支持的输出格式。
 */
function out(string $input, string $encoding = 'UTF-8', string $output_format = 'html'): string
{
    $flags = ENT_QUOTES;

    if ($output_format === 'xml') {
        $flags = ENT_XML1;
    } elseif ($output_format === 'json') {
        // Customize JSON escaping as needed
        $input = json_encode($input, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
        $flags = ENT_NOQUOTES;
    } elseif ($output_format === 'javascript') {
        // JavaScript-encode the input
        $input = json_encode($input, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
    } elseif ($output_format === 'attribute') {
        // HTML属性转义
        $flags = ENT_QUOTES;
    } else {
        // 动态选择正确的功能
        $input = ($output_format === 'html') ? htmlspecialchars($input, $flags, $encoding) : htmlentities($input, $flags, $encoding);
        return $input;
    }

    return htmlspecialchars($input, $flags, $encoding);
}


function session(string $key)
{
    return Session::has($key) ? Session::get($key) : false;
}

function csrf_token(int $length = 32)
{
    return Session::generateToken($length);
}

function url(string $path = ''): string
{
    return Url::home() . filter_var($path, FILTER_SANITIZE_URL);
}

/**
 * 输出 json
 *
 * @param string $msg    提示内容
 * @param string $status 状态，默认是错误，还可设置为 'success'
 */
function json(string $msg, string $status = 'error')
{
    // header('Content-Type: application/json; charset=utf-8');
    echo json_encode(
        [
            'message' => escape($msg),
            'status'  => $status
        ],
        JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE
    );
}

/**
 * 检查请求方法是否为 ajax
 *
 * @return boolean
 */
function is_ajax(): bool
{
    return isset($_SERVER['HTTP_X_REQUESTED_WITH'])
        && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
}

/**
 * 检测是否使用手机访问
 *
 * @return bool
 */
function is_mobile(): bool
{
    if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) {
        return true;
    } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) {
        return true;
    } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) {
        return true;
    } elseif (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $_SERVER['HTTP_USER_AGENT'])) {
        return true;
    } else {
        return false;
    }
}



/**
 * 调试输出
 */
function dump(...$value): void
{
    ob_start();
    var_dump(...$value);
    $output = ob_get_clean();
    $output = preg_replace('/]=>\n(\s+)/m', '] => ', $output);
    $output = '<pre>' . htmlspecialchars($output, ENT_SUBSTITUTE) . '</pre>';
    echo $output;
}

/**
 * 输出并退出
 */
function dd(...$vars): void
{
    dump($vars);
    exit();
}

/**
 * @param  int     $code
 * @param  string  $message
 */
function abort(int $code, $message = '')
{
    if ($code === 404) {
        $message = '页面不存在 。';
        http_response_code(404);
        header('HTTP/1.1 404 Not Found');
        return require ROOT_PATH.'/templates/errors/404.php';
    }

    if ($code === 403) {
        $message = '无访问权限 。';
        http_response_code(403);
        header('HTTP/1.1 403 Forbidden');
        return require ROOT_PATH.'/templates/errors/403.php';
    }
}

/**
 * 检查两个值是否相等，用于确定 radio 或 checkbox 的选中状态。
 * 
 * @param string $val1 比对值1
 * @param string $val2 比对值2
 * @return string 如果两个值相等，则返回 "checked"，否则返回空字符串。  
 */
function is_checked(string $val1, string $val2): string
{
    return $val1 === $val2 ? "checked" : "";
}

/**  
 * 检查选项值是否应被选中，并返回相应的 HTML 属性字符串。  
 *  
 * @param string $optionValue   当前选项的值。  
 * @param string $compareValue  要比较的值。  
 * @return string              如果选项值匹配比较值，则返回 'selected="selected"'，否则返回空字符串。  
 */  
function is_option_selected(string $optionValue, string $compareValue): string
{  
    return $optionValue === $compareValue ? ' selected="selected"' : '';  
}

/**
 * 规划缓存命名
 *
 * @param  string  $name      缓存名称
 * @param  mixed   $parameter 缓存影响参数
 * @param  boolean $isSuper   是否为全局缓存
 * @return string 缓存名称
 */
function set_cache_name($name, $parameter = '', $isSuper = true)
{
    $cacheConfig = config('cache');
    $parameter   = is_array($parameter) ? implode('_', $parameter) : $parameter;
    $cacheName   = $isSuper ? $cacheConfig['prefix'] . $name . $parameter : $cacheConfig['prefix'] . 'CONTROLLER_NAME' . '_' . 'METHOD_NAME' . '_' . $name . $parameter;
    if (empty($cacheConfig['name2md5'])) {
        return $cacheName;
    }
    return md5($cacheName);
}

/**  
 * 可能适用于筛选通过文本区域提交的数据  
 *  
 * @param string $string      要过滤的字符串  
 * @param bool   $strip_tags  是否删除HTML和PHP标记  
 * @param string $allowed_tags 允许的HTML标签，多个标签之间用逗号分隔  
 * @param bool   $html        是否将特殊字符转换为HTML实体  
 *  
 * @return string 过滤后的字符串  
 */  
function filter_string($string, $strip_tags = true, $allowed_tags = '', $html = false)
{  
    // 删除字符串首尾处的空白字符（或者其他字符）
    $string = trim($string);
    // 将多个连续空格转换为一个空格，换行除外
    $string = preg_replace('#[^\S\r\n]+#', ' ', $string);
    // 替换换行符为 <br>
    $string = preg_replace('#[\r\n]+#', '<br>', $string);
    if ($strip_tags) {
        // 删除HTML和PHP标记
        $string = strip_tags($string, $allowed_tags);
    }
    if ($html) {
        // 将特殊字符转换为 HTML 实体
        $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }

    return $string;  
}

/**  
 * 过滤和清理变量
 *
 * @param  mixed $value 要过滤的值，可以是数组、字符串或数字
 * @return mixed 过滤后的值，保持与输入相同的类型
 * @throws Exception 如果输入的值类型不是预期的标量类型或null
 */  
function escape($value = null)
{
    // 检查值是否为非标量或null，并在这种情况下抛出异常
    if (!is_scalar($value) && !is_array($value)) {
        throw new Exception('意外的值类型');
    }

    // 如果值是数组，则递归过滤其元素
    if (is_array($value)) {
        foreach ($value as $k => $v) {
            $value[$k] = sanitizeData($v);
        }
        return $value;
    }

    // 如果值是字符串，则转义HTML特殊字符
    if (is_string($value)) {
        return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }
  
    // 如果值是数字（整数或浮点数），则使用FILTER_SANITIZE_NUMBER_FLOAT过滤器
    if (is_numeric($value)) {
        return filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT);
    }

    // 对于null值，可以选择不进行任何操作或返回特定值（例如空字符串）
    // 在这里，我们选择返回null
    return null;
}


/**  
 * 去除字符串中所有的空白字符
 *
 * @param string $str 需要处理的字符串
 * @return string 处理后的字符串，其中所有空白字符都已被移除
 */
function remove_all_whitespace($str)
{
    // 使用正则表达式匹配所有空白字符（包括空格、制表符、换行符等）并替换为空字符串
    return preg_replace('/\s+/u', '', $str);
}


/**
 * 友好的时间显示
 */
function nice_time($time) {
    $timeDiff = time() - $time;

    // 时间单位（秒）和对应的描述
    $units = [
        31536000 => '年',
        2592000  => '个月',
        604800   => '周',
        86400    => '天',
        3600     => '小时',
        60       => '分钟',
        1        => '秒'
    ];  

    foreach ($units as $seconds => $unit) {
        if ($timeDiff >= $seconds) {
            $count = floor($timeDiff / $seconds);
            return $count . '<span class="time-text">' . $unit . '前</span>';
        }
    }

    // 如果由于某种原因没有返回任何值，可以添加一个默认返回值
    return '<span class="time-text">未知时间</span>';
}

/**  
 * 格式化文件大小
 *
 * @param  int|float $size 文件大小（字节）
 * @param  int $precision 保留的小数位数
 * @return string 格式化的文件大小
 */
function format_size($size, $precision = 2)
{
    // 定义单位数组
    $units = [' 字节', ' KB', ' MB', ' GB', ' TB'];

    // 通过除以 1024 来找到合适的单位
    for ($i = 0; $size > 1024 && $i < 4; $i++) {
        $size /= 1024;
    }

    // 返回格式化的文件大小
    return round($size, $precision) . $units[$i];
}

/**
 * 登录或注册后跳转到之前页面
 */
function go_back()
{
    if (session('backToLink')) {
        return redirect(session('backToLink'));
    }

    return redirect(url('/'));
}

/**  
 * 生成一个指定长度的唯一ID（类似于UUID）
 *
 * @param int $length 生成ID的长度，默认为20
 * @return string 返回生成的唯一ID字符串
 */
function uuid(int $length = 20): string {
    $bytes = random_bytes((int)ceil($length / 2));
    return substr(bin2hex($bytes), 0, $length);
}

/**  
 * 随机显示颜色
 *
 * @return string 返回一个随机颜色的十六进制代码
 */
function rand_color(): string
{
    $colors = ['#F5FBF7', '#FEF7E0', '#F8F9FA', '#D9534F', '#B37333', '#00ABA9'];
    return $colors[array_rand($colors)];
}
